package net.sureon.common.mail;

import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

import net.sureon.common.config.MailConfig;
import net.sureon.common.config.SureonConfig;
import net.sureon.common.exception.ExceptionKeys;
import net.sureon.common.exception.SureonException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * This class handles all the interaction with JavaMail and send out the mail. The mail
 * server is specified in the config file under the "mail" category
 *          mail.smtp.host - Is the mail host used to send out the mail
 *          mail.smtp.auth - If authorization is required
 *          mail.smtp.username - The username for the authorization
 *          mail.smtp.password - The password for the authorization
 */
public class MailSender {
	private static final Log LOGGER = LogFactory.getLog(MailSender.class);

    private static final String CATEGORY_MAIL = "mail";
    private static final String PROP_SMTP_HOST = "mail.smtp.host";
    private static final String PROP_SMTP_AUTH = "mail.smtp.auth";
    private static final String PROP_SMTP_USERNAME = "mail.smtp.username";
    private static final String PROP_SMTP_PASSWORD = "mail.smtp.password";
    private Properties mailProp;

    /**
     * Default constructor
     *
     * @throws SureonException
     */
    public MailSender() throws SureonException {
        mailProp = new Properties();
        MailConfig config = SureonConfig.getInstance().getMailConfig();
        mailProp.setProperty(PROP_SMTP_HOST, config.getSmtpHost());
        mailProp.setProperty(PROP_SMTP_AUTH, config.getAuth());
        mailProp.setProperty(PROP_SMTP_USERNAME, config.getUserName());
        mailProp.setProperty(PROP_SMTP_PASSWORD, config.getPassword());
    }

    /**
     * Constructor that takes in the mail properties. At least two properites
     * are needed, mail.smtp.host and mail.smtp.auth
     *
     * @param prop
     */
    public MailSender(Properties prop)  {
        mailProp = prop;
    }

    /**
     * Converts the MailData and the mail content into an email and then sends
     * it out
     *
     * @param maildata
     * @param to
     * @param content
     * @throws SureonException
     */
    public void send(MailData maildata, String to, String content) throws SureonException{
        try {
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Starting to send mail using to - " +  to);
            }
            Session mailSession;
            if (isSmtpAuth()) {
                mailSession = Session.getInstance(mailProp, getAuthenticator());
            } else {
                if(LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Not configured for authentication");
                }
                mailProp.put(PROP_SMTP_AUTH, "false");
                mailSession = Session.getInstance(mailProp);
            }
            if(LOGGER.isDebugEnabled()) {
                mailSession.setDebug(true);
            }
            MimeMessage msg = new MimeMessage(mailSession);
            msg.setFrom(new InternetAddress(encodeMailAddress(maildata.getFrom(),maildata.getFromName()))) ;

            // Reply to
            String[] replyTo = maildata.getReplyTo();
            if (replyTo != null && replyTo.length > 0) {
                if(LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Setting replyTo " +  replyTo);
                }
                InternetAddress[] addressReplyTo = new InternetAddress[replyTo.length];
                for (int i = 0; i < replyTo.length; i++) {
                    addressReplyTo[i] = new InternetAddress(replyTo[i]);
                }
                msg.setReplyTo(addressReplyTo);
            }

            // To
            if (to == null) {
                throw new Exception("Mail have no recipient!");
            }
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Setting To " +  to);
            }
            msg.addRecipients(Message.RecipientType.TO, to);

            // CC
            String[] cc = maildata.getCc();
            if (cc != null) {
                if(LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Setting CC " +  cc);
                }
                for (int i = 0; i < cc.length; i++) {
                    msg.addRecipients(Message.RecipientType.CC, cc[i]);
                }
            }

            // BCC
            String[] bcc = maildata.getBcc();
            if (bcc != null) {
                if(LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Setting DCC " +  bcc);
                }
                for (int i = 0; i < bcc.length; i++) {
                    msg.addRecipients(Message.RecipientType.BCC, bcc[i]);
                }
            }
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Setting subject " +  maildata.getSubject());
            }
            msg.setSubject(maildata.getSubject(),"UTF-8");

            // Importance
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Setting importance " +  String.valueOf(maildata.getImportance()));
            }
            msg.addHeader("X-Priority", String.valueOf(maildata.getImportance()));


            // Body
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Setting the content " +  content);
            }
            MimeBodyPart messageBodyPart = new MimeBodyPart();
            messageBodyPart.setContent(content, maildata.getMailFormat()+ ";charset=UTF-8");
            MimeMultipart multipart = new MimeMultipart();
            multipart.addBodyPart(messageBodyPart);

            // Attachments
            List attachments = maildata.getAttachments();
            if (attachments != null && attachments.size() > 0) {
                BodyPart bodyPart = new MimeBodyPart();
                Iterator it = attachments.iterator();
                while(it.hasNext()){
                    Object attachObj = it.next();
                    if(LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Setting the attachment " +  attachObj.toString());
                    }
                    if(attachObj instanceof String){
                        FileDataSource fileds = new FileDataSource(attachObj.toString());
                        bodyPart.setDataHandler(new DataHandler(fileds));
                        bodyPart.setFileName(fileds.getName());
                        multipart.addBodyPart(bodyPart);
                    } else if(attachObj instanceof FileDataSource){
                        bodyPart.setDataHandler(new DataHandler((FileDataSource)attachObj));
                        bodyPart.setFileName(((FileDataSource)attachObj).getName());
                        multipart.addBodyPart(bodyPart);
                    }
                }
            }
            msg.setContent(multipart);
            msg.saveChanges();

            // Now send
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("Sending .... ");
            }
            Transport transport = mailSession.getTransport("smtp");
            if (isSmtpAuth()) {
                transport.connect(mailProp.getProperty(PROP_SMTP_HOST),
                            mailProp.getProperty(PROP_SMTP_USERNAME),
                            mailProp.getProperty(PROP_SMTP_PASSWORD));
            } else {
                transport.connect();
            }
            transport.sendMessage(msg, msg.getAllRecipients());
            transport.close();
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug(".... Done. ");
            }
            if(LOGGER.isInfoEnabled()) {
                LOGGER.info("Mail was successfully sent out to " + to + " with the subject " + maildata.getSubject());
            }
        } catch (Exception e) {
            LOGGER.error("Error in Mail Sender", e);
            throw new SureonException(ExceptionKeys.MAIL_FAILED_TO_SEND, e);
        }

    }

    private boolean isSmtpAuth(){
        String value = mailProp.getProperty(PROP_SMTP_AUTH);
        if(value == null){
            return false;
        }
        return Boolean.valueOf(value);
    }

    private MailAuthenticator getAuthenticator(){
        String username = mailProp.getProperty(PROP_SMTP_USERNAME);
        String password = mailProp.getProperty(PROP_SMTP_PASSWORD);
        return new MailAuthenticator(username, password);
    }

    private class MailAuthenticator extends Authenticator {
        private String username, password;

        /**
         * @param username String
         * @param password String
         */
        public MailAuthenticator(String username, String password) {
            this.username = username;
            this.password = password;
        }

        /**
         * @see javax.mail.Authenticator#getPasswordAuthentication()
         */
        public PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password);
        }

    }

    /**
     * Combines the Alias and email address
     * @param address
     * @param alias
     * @return
     */
    private String encodeMailAddress(String address, String alias) {
        String mailAddress = "";
        String alias1 = "";
        address = address.replaceAll("&lt;", "<");
        address = address.replaceAll("&gt;", ">");
        int index = address.indexOf("<");
        if (index >= 0 && address.endsWith(">")) {
           if (address.startsWith("\"")) {
               alias1 = address.substring(1, index - 1);
           } else {
               alias1 = address.substring(0, index);
           }
           address = address.substring(index + 1, address.length() - 1);
        }
        if ((alias == null || alias.equals("")) && !alias1.equals("")) {
            alias = alias1;
        }
        address = address.trim();
        if (alias == null || alias.trim().equals("")) {
            mailAddress = address;
        } else {
            alias = alias.trim();
            mailAddress = "\"" + alias + "\"" + "<" + address + ">";
        }
        return mailAddress;
    }
}